using System;
using System.Collections.Generic;

using Silk.NET.OpenGLES;
using TTEngine.Resource;

namespace TTEngine.Graphics
{

    public class VertexAttribItem
    {
        public VertexAttribPointerType Type { get; set; }
        public int Size { get; set; } // 元素数量
        public bool Normalize { get; set; }
        public uint Offset { get; set; }

        public VertexAttribItem(VertexAttribPointerType type, int size, bool normalize)
        {
            Type = type;
            Size = size;
            Normalize = normalize;
        }
    }

    public class VBOInfo
    {
        public uint VertexAttribDivisor { get; set; } = 0;
        public List<VertexAttribItem> Attribs { get; set; } = new List<VertexAttribItem>();
        public int AttribsCount { get; set; }
        public uint Stride { get; set; }
        public string Hash { get; set; }

        public void Update()
        {
            AttribsCount = Attribs.Count;
            Stride = 0;
            Hash = "";
            foreach (var a in Attribs)
            {
                a.Offset = Stride;
                switch (a.Type)
                {
                    case VertexAttribPointerType.Float:
                        Stride += (uint)(4 * a.Size);
                        break;
                    case VertexAttribPointerType.UnsignedByte:
                        Stride += (uint)(1 * a.Size);
                        break;
                    case VertexAttribPointerType.UnsignedShort:
                        Stride += (uint)(2 * a.Size);
                        break;
                    case VertexAttribPointerType.UnsignedInt:
                        Stride += (uint)(4 * a.Size);
                        break;
                    default:
                        throw new Exception("Unknown stride");
                }
                Hash += $"{a.Type}({a.Size});";
            }
        }
    }

    public class VertexFormat
    {
        public string Id { get; set; }
        public List<VBOInfo> VBOs { get; set; } = new List<VBOInfo>();
        public string Hash { get; set; }

        public VertexFormat(string id)
        {
            Id = id;
        }

        public void Update()
        {
            Hash = "";
            foreach (var vbo in VBOs)
            {
                vbo.Update();
                Hash += "{" + vbo.Hash + "}";
            }
        }
    }

    public class VertexFormatMgr
    {
        private static Dictionary<string, VertexFormat> _vertexFormats = new Dictionary<string, VertexFormat>();
        private static Dictionary<string, string> _vertexFormatsID2Hash = new Dictionary<string, string>();
        private static VertexFormat _vertexFormat_Vertex_Normal;
        private static VertexFormat _vertexFormat_Vertex_Color;
        private static VertexFormat _vertexFormat_Vertex_UV;
        private static VertexFormat _vertexFormat_Vertex_UV_Color;
        private static VertexFormat _vertexFormat_Vertex_UV_Color_Color2;
        private static VertexFormat _vertexFormat_Vertex_UV_Color_InstPos;
        private static VertexFormat _vertexFormat_Vertex_UV_Color_InstPosNormal;
        private static VertexFormat _vertexFormat_Vertex_UV_Color_InstXYZRUVRectColor;
        private static VertexFormat _vertexFormat_Vertex_InstFull;

        public static VertexFormat RegFormat(VertexFormat format)
        {
            format.Update();
            if (!_vertexFormats.ContainsKey(format.Hash))
            {
                _vertexFormats[format.Hash] = format;
            }
            else
            {
                format = _vertexFormats[format.Hash]; // 已经存在,节约一下
            }
            _vertexFormatsID2Hash[format.Id] = format.Hash;
            return format;
        }

        public static VertexFormat GetFormat(string id)
        {
            if (_vertexFormatsID2Hash.TryGetValue(id, out string hash))
            {
                return _vertexFormats[hash];
            }
            return null;
        }

        public static VertexFormat GetFormat_Vertex_Normal()
        {
            if (_vertexFormat_Vertex_Normal == null)
            {
                var vecf = new VertexFormat("Vertex_Normal");
                var vbo = new VBOInfo();
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, true));
                vecf.VBOs.Add(vbo);
                _vertexFormat_Vertex_Normal = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_Normal;
        }

        public static VertexFormat GetFormat_Vertex_Color()
        {
            if (_vertexFormat_Vertex_Color == null)
            {
                var vecf = new VertexFormat("Vertex_Color");
                var vbo = new VBOInfo();
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true));
                vecf.VBOs.Add(vbo);
                _vertexFormat_Vertex_Color = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_Color;
        }

        public static VertexFormat GetFormat_Vertex_UV()
        {
            if (_vertexFormat_Vertex_UV == null)
            {
                var vecf = new VertexFormat("Vertex_UV_Color");
                var vbo = new VBOInfo();
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false));
                vecf.VBOs.Add(vbo);
                _vertexFormat_Vertex_UV = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_UV;
        }

        public static VertexFormat GetFormat_Vertex_UV_Color()
        {
            if (_vertexFormat_Vertex_UV_Color == null)
            {
                var vecf = new VertexFormat("Vertex_UV_Color");
                var vbo = new VBOInfo();
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true));
                vecf.VBOs.Add(vbo);
                _vertexFormat_Vertex_UV_Color = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_UV_Color;
        }

        public static VertexFormat GetFormat_Vertex_UV_Color_Ext()
        {
            if (_vertexFormat_Vertex_UV_Color_Color2 == null)
            {
                var vecf = new VertexFormat("Vertex_UV_Color_Color2");
                var vbo = new VBOInfo();
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, false));
                vbo.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 4, false));
                vecf.VBOs.Add(vbo);
                _vertexFormat_Vertex_UV_Color_Color2 = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_UV_Color_Color2;
        }

        public static VertexFormat GetFormat_Vertex_UV_Color_InstPos()
        {
            if (_vertexFormat_Vertex_UV_Color_InstPos == null)
            {
                var vecf = new VertexFormat("Vertex_UV_Color_InstPos");
                var vbo1 = new VBOInfo();
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false));
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true));
                vecf.VBOs.Add(vbo1);

                var vbo2 = new VBOInfo();
                vbo2.VertexAttribDivisor = 1; // instanced data
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vecf.VBOs.Add(vbo2);

                _vertexFormat_Vertex_UV_Color_InstPos = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_UV_Color_InstPos;
        }

        public static VertexFormat GetFormat_Vertex_UV_Color_InstPosNormal()
        {
            if (_vertexFormat_Vertex_UV_Color_InstPosNormal == null)
            {
                var vecf = new VertexFormat("Vertex_UV_Color_InstPosNormal");
                var vbo1 = new VBOInfo();
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false));
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true));
                vecf.VBOs.Add(vbo1);

                var vbo2 = new VBOInfo();
                vbo2.VertexAttribDivisor = 1; // instanced data
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, true));
                vecf.VBOs.Add(vbo2);

                _vertexFormat_Vertex_UV_Color_InstPosNormal = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_UV_Color_InstPosNormal;
        }

        public static VertexFormat GetFormat_Vertex_InstFull()
        {
            if (_vertexFormat_Vertex_InstFull == null)
            {
                var vecf = new VertexFormat("Vertex_InstFull");
                var vbo1 = new VBOInfo();
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false)); // basemesh uv only
                vecf.VBOs.Add(vbo1);

                var vbo2 = new VBOInfo();
                vbo2.VertexAttribDivisor = 1; // instanced data
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 4, false)); // Pos xyz + rotate
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false)); // Scale
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true)); // color
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedShort, 2, false)); // INSTid&ext
                vecf.VBOs.Add(vbo2);

                _vertexFormat_Vertex_InstFull = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_InstFull;
        }

        public static VertexFormat GetFormat_Vertex_UV_Color_InstXYZRUVRectColor()
        {
            if (_vertexFormat_Vertex_UV_Color_InstXYZRUVRectColor == null)
            {
                var vecf = new VertexFormat("Vertex_UV_Color_InstXYZRUVRectColor");
                var vbo1 = new VBOInfo();
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 3, false));
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false));
                vbo1.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true));
                vecf.VBOs.Add(vbo1);

                var vbo2 = new VBOInfo();
                vbo2.VertexAttribDivisor = 1; // instanced data
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 4, false)); // xyzr
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 4, false)); // uvrect xywh
                vbo2.Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true)); // color
                vecf.VBOs.Add(vbo2);

                _vertexFormat_Vertex_UV_Color_InstXYZRUVRectColor = RegFormat(vecf);
            }
            return _vertexFormat_Vertex_UV_Color_InstXYZRUVRectColor;
        }
    }

    public class Mesh : ResourceObj
    {
        public Mesh(ResourcePackage package)
            : base(package)
        {

        }
        protected override void OnDestory()
        {
            if (_vbos != null)
            {
                for (var i = 0; i < _vbos.Length; i++)
                {
                    if (_vbos[i] > 0)
                        TTEngine.Runtime.activeGL.DeleteBuffer(_vbos[i]);
                    _vbos[i] = 0;
                }
            }
            if (_ebo > 0)
            {
                Runtime.activeGL.DeleteBuffer(_ebo);
                _ebo = 0;
            }
            if (_vao > 0)
            {
                Runtime.activeGL.DeleteVertexArray(_vao);
                _vao = 0;
            }

        }
        private uint[] _vbos;
        public uint InstanceCount { get; set; } // 实例数量
        private uint _ebo;
        private uint _vao;
        private uint[] _vertexCount;
        public uint[] VertexCount
        {
            get => _vertexCount;
            set => _vertexCount = value;
        }
        public uint IndexCount { get; set; }
        public PrimitiveType Mode { get; set; } = PrimitiveType.Triangles; // GL_TRIANGLES
        private VertexFormat _vertexFormat;

        public VertexFormat GetVertexFormat()
        {
            return _vertexFormat;
        }

        public void UpdateVertexFormat(VertexFormat vecf)
        {
            var gl = TTEngine.Runtime.activeGL;
            if (_vao == 0)
            {
                _vao = gl.GenVertexArray();
            }

            if (_vbos == null)
            {
                _vbos = new uint[vecf.VBOs.Count];
                _vertexCount = new uint[vecf.VBOs.Count];
            }

            while (_vbos.Length < vecf.VBOs.Count)
            {
                Array.Resize(ref _vbos, vecf.VBOs.Count);
                Array.Resize(ref _vertexCount, vecf.VBOs.Count);
            }

            for (int j = 0; j < vecf.VBOs.Count; j++)
            {
                if (_vbos[j] == 0)
                {
                    _vbos[j] = gl.GenBuffer();
                }
            }

            _vertexFormat = vecf;

            // 初始化 vao
            gl.BindVertexArray(_vao);

            uint iatt = 0;
            for (int j = 0; j < vecf.VBOs.Count; j++)
            {
                var vf = vecf.VBOs[j];
                gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbos[j]); // GL_ARRAY_BUFFER

                for (int i = 0; i < vf.AttribsCount; i++)
                {
                    var vi = vf.Attribs[i];

                    gl.EnableVertexAttribArray(iatt);
                    gl.VertexAttribPointer(iatt, vi.Size, vi.Type, vi.Normalize, vf.Stride, (IntPtr)vi.Offset);
                    gl.VertexAttribDivisor(iatt, vf.VertexAttribDivisor);
                    iatt++;
                }
            }

            for (uint i = iatt; i < 10; i++)
            {
                gl.DisableVertexAttribArray(i);
            }

            gl.BindVertexArray(0);
        }


        public void UploadVertexBuffer<T>(int vboindex, T[] vertexdata, bool dynamic, int bytelength) where T : unmanaged
        {
            var gl = TTEngine.Runtime.activeGL;
            if (_vbos[vboindex] == 0)
            {
                _vbos[vboindex] = gl.GenBuffer();
            }

            gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbos[vboindex]); // GL_ARRAY_BUFFER
            unsafe
            {
                fixed (void* ptr = vertexdata)
                {
                    gl.BufferData(BufferTargetARB.ArrayBuffer, (System.UIntPtr)bytelength, ptr, dynamic ? BufferUsageARB.DynamicDraw : BufferUsageARB.StaticDraw); // GL_DYNAMIC_DRAW : GL_STATIC_DRAW

                }
            }
            _vertexCount[vboindex] = (uint)(bytelength / _vertexFormat.VBOs[vboindex].Stride);
            gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0); // GL_ARRAY_BUFFER
        }

        public void UploadIndexBuffer(byte[] element, bool dynamic, uint bytelength)
        {
            var gl = TTEngine.Runtime.activeGL;
            if (_ebo == 0)
            {
                _ebo = gl.GenBuffer();
            }

            gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ebo); // GL_ELEMENT_ARRAY_BUFFER
            unsafe
            {
                fixed (void* ptr = element)
                {
                    gl.BufferData(BufferTargetARB.ElementArrayBuffer, (System.UIntPtr)bytelength, ptr, dynamic ? BufferUsageARB.DynamicDraw : BufferUsageARB.StaticDraw); // GL_DYNAMIC_DRAW : GL_STATIC_DRAW

                }
            }
            IndexCount = bytelength / 2;
            gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, 0); // GL_ELEMENT_ARRAY_BUFFER
        }

        public void Apply()
        {
            var gl = TTEngine.Runtime.activeGL;
            gl.BindVertexArray(_vao);
            if (_ebo != 0)
            {
                gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ebo); // GL_ELEMENT_ARRAY_BUFFER
            }
        }

        public void Draw(Material mat)
        {
            var gl = TTEngine.Runtime.activeGL;
            var mode = this.Mode;
            mat.Apply();

            this.Apply();

            if (this._ebo == 0)
            {
                gl.DrawArrays(mode, 0, this.VertexCount[0]);
            }
            else
            {
                unsafe
                {
                    gl.DrawElements(mode, this.IndexCount, DrawElementsType.UnsignedShort, null); // GL_UNSIGNED_SHORT
                }
            }
        }

        public void DrawInstanced(Material mat)
        {
            var gl = TTEngine.Runtime.activeGL;
            var mode = this.Mode;
            mat.Apply();

            this.Apply();

            if (this._ebo == 0)
            {
                gl.DrawArraysInstanced(mode, 0, this.VertexCount[0], this.InstanceCount);
            }
            else
            {
                unsafe
                {
                    gl.DrawElementsInstanced(mode, this.IndexCount, DrawElementsType.UnsignedShort, null, this.InstanceCount); // GL_UNSIGNED_SHORT
                }
            }
        }
    }
}